/*
** Xin YUAN, 2024, BSD (2)
*/

////////////////////////////////////////////////////////////////////////////////

//identity

template <typename T>
inline Mat2x2<T> Mat2x2_Identity() noexcept
{
	return Mat2x2<T>((T)1, (T)0, (T)0, (T)1);
}
template <typename T>
inline Mat3x3<T> Mat3x3_Identity() noexcept
{
	return Mat3x3<T>(
			(T)1, (T)0, (T)0,
			(T)0, (T)1, (T)0,
			(T)0, (T)0, (T)1);
}
template <typename T>
inline Mat4x4<T> Mat4x4_Identity() noexcept
{
	return Mat4x4<T>(
			(T)1, (T)0, (T)0, (T)0,
			(T)0, (T)1, (T)0, (T)0,
			(T)0, (T)0, (T)1, (T)0,
			(T)0, (T)0, (T)0, (T)1);
}

//basic transform

template <typename T>
inline Mat4x4<T> Mat_Translation(const T& x, const T& y, const T& z) noexcept
{
	return Mat4x4<T>(
			(T)1, (T)0, (T)0, x,
			(T)0, (T)1, (T)0, y,
			(T)0, (T)0, (T)1, z,
			(T)0, (T)0, (T)0, (T)1);
}
template <typename T>
inline Mat4x4<T> Mat_Translation(const Vec3<T>& v) noexcept
{
	return Mat_Translation(v.v._1, v.v._2, v.v._3);
}

/*
left hand or right hand:
The thumb points in the positive direction of the rotation axis,
other fingers are oriented in a positive direction.
If the vector is later multiplied by a matrix (v * M),
the rotation matrices in both cases take the same form.
*/
template <typename T>
inline Mat4x4<T> Mat_Rotation(const T& angle, const Vec3<T>& axis) noexcept
{
	T const c = FloatTraits<T>::cos(angle);
	T const s = FloatTraits<T>::sin(angle);

	Vec3<T> n_axis(Vec_Normalize(axis));
	Vec3<T> temp(((T)1 - c) * n_axis);

	return Mat4x4<T>(
			c + temp.data[0] * n_axis.data[0],
			temp.data[1] * n_axis.data[0] - s * n_axis.data[2],
			temp.data[2] * n_axis.data[0] + s * n_axis.data[1],
			(T)0,
			temp.data[0] * n_axis.data[1] + s * n_axis.data[2],
			c + temp.data[1] * n_axis.data[1],
			temp.data[2] * n_axis.data[1] - s * n_axis.data[0],
			(T)0,
			temp.data[0] * n_axis.data[2] - s * n_axis.data[1],
			temp.data[1] * n_axis.data[2] + s * n_axis.data[0],
			c + temp.data[2] * n_axis.data[2],
			(T)0,
			(T)0, (T)0, (T)0, (T)1);
}
template <typename T>
inline Mat4x4<T> Mat_RotationX(const T& angle) noexcept
{
	T const c = FloatTraits<T>::cos(angle);
	T const s = FloatTraits<T>::sin(angle);

	return Mat4x4<T>(
			T(1), T(0),  T(0), T(0),
			T(0), c,    -s,    T(0),
			T(0), s,     c,    T(0),
			T(0), T(0),  T(0), T(1));
}
template <typename T>
inline Mat4x4<T> Mat_RotationY(const T& angle) noexcept
{
	T const c = FloatTraits<T>::cos(angle);
	T const s = FloatTraits<T>::sin(angle);

	return Mat4x4<T>(
			c,    T(0), s,    T(0),
			T(0), T(1), T(0), T(0),
			-s,   T(0), c,    T(0),
			T(0), T(0), T(0), T(1));
}
template <typename T>
inline Mat4x4<T> Mat_RotationZ(const T& angle) noexcept
{
	T const c = FloatTraits<T>::cos(angle);
	T const s = FloatTraits<T>::sin(angle);

	return Mat4x4<T>(
			c,    -s,    T(0), T(0),
			s,     c,    T(0), T(0),
			T(0),  T(0), T(1), T(0),
			T(0),  T(0), T(0), T(1));
}

template <typename T>
inline Mat4x4<T> Mat_Scale(const T& sx, const T& sy, const T& sz) noexcept
{
	return Mat4x4<T>(
			sx,   T(0), T(0), T(0),
			T(0), sy,   T(0), T(0),
			T(0), T(0), sz,   T(0),
			T(0), T(0), T(0), T(1));
}
template <typename T>
inline Mat4x4<T> Mat_Scale(const Vec3<T>& v) noexcept
{
	return Mat_Scale(v.v._1, v.v._2, v.v._3);
}

/*
vp Point of shearing as reference.
lx Ratio of matrix.x projection in YZ plane relative to the y-axis/z-axis.
ly Ratio of matrix.y projection in XZ plane relative to the x-axis/z-axis.
lz Ratio of matrix.z projection in XY plane relative to the x-axis/y-axis.
*/
template <typename T>
inline Mat4x4<T> Mat_Shear(const Vec3<T>& vp, const Vec2<T>& lx, const Vec2<T>& ly, const Vec2<T>& lz) noexcept
{
	T const l_xy = lx.data[0];
	T const l_xz = lx.data[1];
	T const l_yx = ly.data[0];
	T const l_yz = ly.data[1];
	T const l_zx = lz.data[0];
	T const l_zy = lz.data[1];

	Vec3<T> p_l(l_xy + l_xz, l_yx + l_yz, l_zx + l_zy);

	return Mat4x4<T>(
		T(1), l_xy, l_xz, -p_l.data[0] * vp.data[0],
		l_yx, T(1), l_yz, -p_l.data[1] * vp.data[1],
		l_zx, l_zy, T(1), -p_l.data[2] * vp.data[2],
		T(0), T(0), T(0), T(1));
}

//view transform

template <typename T>
inline Mat4x4<T> Mat_LookAtRH(const Vec3<T>& eye, const Vec3<T>& at, const Vec3<T>& up) noexcept
{
	Vec3<T> const f(Vec_Normalize(at - eye));
	Vec3<T> const s(Vec_Normalize(f.Cross(up)));
	Vec3<T> const u(s.Cross(f));
	return Mat4x4<T>(
			s.x,   s.y,  s.z, -s.Dot(eye),
			u.x,   u.y,  u.z, -u.Dot(eye),
			-f.x, -f.y, -f.z,  f.Dot(eye),
			T(0),  T(0), T(0), T(1));
}
template <typename T>
inline Mat4x4<T> Mat_LookAtLH(const Vec3<T>& eye, const Vec3<T>& at, const Vec3<T>& up) noexcept
{
	Vec3<T> const f(Vec_Normalize(at - eye));
	Vec3<T> const s(Vec_Normalize(up.Cross(f)));
	Vec3<T> const u(f.Cross(s));
	return Mat4x4<T>(
			s.x,  s.y,  s.z, -s.Dot(eye),
			u.x,  u.y,  u.z, -u.Dot(eye),
			f.x,  f.y,  f.z, -f.Dot(eye),
			T(0), T(0), T(0), T(1));
}

//projection

//to normalized device coordinates (NDC):
//[-1, 1], [-1, 1], [-1, 1](the same as Direct3D z-axis which points to inside the screen)
//zn >= 0, zf > 0, zf > zn

template <typename T>
inline Mat4x4<T> Mat_OrthoLH(const T& w, const T& h, const T& zn, const T& zf) noexcept
{
	return Mat4x4<T>(
			T(2) / w, T(0),     T(0),              T(0),
			T(0),     T(2) / h, T(0),              T(0),
			T(0),     T(0),     T(2) / (zf - zn), -(zf + zn) / (zf - zn),
			T(0),     T(0),     T(0),              T(1));
}
template <typename T>
inline Mat4x4<T> Mat_OrthoRH(const T& w, const T& h, const T& zn, const T& zf) noexcept
{
	return Mat4x4<T>(
			T(2) / w, T(0),      T(0),              T(0),
			T(0),     T(2) / h,  T(0),              T(0),
			T(0),     T(0),     -T(2) / (zf - zn), -(zf + zn) / (zf - zn),
			T(0),     T(0),      T(0),              T(1));
}

//aspect = w / h
template <typename T>
inline Mat4x4<T> Mat_PerspectiveFovRH(const T& fovy, const T& aspect, const T& zn, const T& zf) noexcept
{
	T const tanHalfFovy = FloatTraits<T>::tan(fovy / T(2));
	return Mat4x4<T>(
			T(1) / (aspect * tanHalfFovy), T(0),                T(0),                   T(0),
			T(0),                          T(1) / tanHalfFovy,  T(0),                   T(0),
			T(0),                          T(0),               -(zf + zn) / (zf - zn), -(T(2) * zf * zn) / (zf - zn),
			T(0),                          T(0),               -T(1),                   T(0));
}
template <typename T>
inline Mat4x4<T> Mat_PerspectiveFovLH(const T& fovy, const T& aspect, const T& zn, const T& zf) noexcept
{
	T const tanHalfFovy = FloatTraits<T>::tan(fovy / T(2));
	return Mat4x4<T>(
			T(1) / (aspect * tanHalfFovy), T(0),               T(0),                   T(0),
			T(0),                          T(1) / tanHalfFovy, T(0),                   T(0),
			T(0),                          T(0),               (zf + zn) / (zf - zn), -(T(2) * zf * zn) / (zf - zn),
			T(0),                          T(0),               T(1),                   T(0));
}

template <typename T>
inline Mat4x4<T> Mat_PerspectiveRH(const T& w, const T& h, const T& zn, const T& zf) noexcept
{
	return Mat4x4<T>(
			T(2) * zn / w, T(0),           T(0),                   T(0),
			T(0),          T(2) * zn / h,  T(0),                   T(0),
			T(0),          T(0),          -(zf + zn) / (zf - zn), -(T(2) * zf * zn) / (zf - zn),
			T(0)           T(0),          -T(1),                   T(0));
}
template <typename T>
inline Mat4x4<T> Mat_PerspectiveLH(const T& w, const T& h, const T& zn, const T& zf) noexcept
{
	return Mat4x4<T>(
			T(2) * zn / w, T(0),          T(0),                   T(0),
			T(0),          T(2) * zn / h, T(0),                   T(0),
			T(0),          T(0),          (zf + zn) / (zf - zn), -(T(2) * zf * zn) / (zf - zn),
			T(0),          T(0),          T(1),                   T(0));
}

//transform : M * V * P (MVP)

//viewport

template <typename T>
inline Mat4x4<T> Mat_Viewport(const T& l, const T& t, const T& w, const T& h) noexcept
{
	return Mat4x4<T>(
			w / T(2),  T(0),     T(0), l + w / T(2),
			T(0),     -h / T(2), T(0), t + h / T(2),
			T(0),      T(0),     T(1), T(0),
			T(0),      T(0),     T(0), T(1));
}

////////////////////////////////////////////////////////////////////////////////
